local modname = "wechat_proxy_access_token"
local _M = { _VERSION = '0.0.1' }
_G[modname] = _M

local ngx_log = ngx.log
local ngx_timer_at = ngx.timer.at

local cjson = require("cjson")

local updateurl = "https://api.weixin.qq.com/cgi-bin/token"
local ticketurl = "https://api.weixin.qq.com/cgi-bin/ticket/getticket"

local updateTime = wechat_config.accessTokenUpdateTime or 6000
local pollingTime = wechat_config.accessTokenPollingTime or 600

local mt = {
  __call = function(_)
    local updateAccessToken
    updateAccessToken = function()
      require("resty.wechat.utils.redis"):connect(wechat_config.redis):lockProcess(
        "accessTokenLocker",
        function(weredis)
          ngx_log(ngx.INFO, "execute accessTokenLocker")
          local app_set, err = weredis.redis:smembers("app_set")
          if err then
            ngx_log(ngx.ERR, "fail to get app_set : ", err)
            return
          end
          for index, app_key in ipairs(app_set) do
            repeat
              ngx_log(ngx.INFO, "begin handle app=[" .. tostring(app_key) .. "]")
              local app_id, err = weredis.redis:hget("app_data:" .. app_key, "app_id")
              if app_id == ngx.null or err then
                ngx_log(ngx.ERR, "fail to get app=[" .. app_key .. "]'s app_id : ", err)
                break
              end
              local app_secret, err = weredis.redis:hget("app_data:" .. app_key, "app_secret")
              if app_secret == ngx.null or err then
                ngx_log(ngx.ERR, "fail to get app=[" .. app_key .. "]'s app_secret : ", err)
                break
              end
              ngx_log(ngx.INFO, "app_id=[" .. tostring(app_id) .. "],app_secret=[" .. tostring(app_secret) .. "]")

              -- 开始获取access_token

              local accessTokenKey = "access_token:" .. app_key
              local jsapiTicketKey = "ticket:" .. app_key

              ngx_log(ngx.INFO, "accessTokenKey=[" .. accessTokenKey .. "],ttl=[" .. weredis.redis:ttl(accessTokenKey) .. "]")

              if 0 < tonumber(weredis.redis:ttl(accessTokenKey) or 0) then
                break
              end

              -- access_token time out, refresh

              local updateparam = {
                method = "GET",
                query = {
                  grant_type = "client_credential",
                  appid = app_id,
                  secret = app_secret,
                },
                ssl_verify = false,
                headers = { ["Content-Type"] = "application/x-www-form-urlencoded" },
              }

              local res, err = require("resty.wechat.utils.http").new():request_uri(updateurl, updateparam)
              if not res or err or tostring(res.status) ~= "200" then
                ngx_log(ngx.ERR, "failed to update access token: ", err or tostring(res.status))
                break
              end
              local resbody = cjson.decode(res.body)
              if not resbody.access_token then
                ngx_log(ngx.ERR, "failed to update access token: ", res.body)
                break
              end

              local ok, err = weredis.redis:setex(accessTokenKey, updateTime - 1, resbody.access_token)
              if not ok then
                ngx_log(ngx.ERR, "failed to set access token: ", err)
                break
              end

              ngx_log(ngx.NOTICE, "succeed to set access token: ", res.body)

              -- refresh jsapi_ticket after refresh access_token

              local ticketparam = {
                method = "GET",
                query = {
                  type = "jsapi",
                },
                ssl_verify = false,
                headers = { ["Content-Type"] = "application/x-www-form-urlencoded" },
              }

              ticketparam.query.access_token = resbody.access_token
              local res, err = require("resty.wechat.utils.http").new():request_uri(ticketurl, ticketparam)
              ticketparam.query.access_token = nil
              if not res or err or tostring(res.status) ~= "200" then
                ngx_log(ngx.ERR, "failed to update jsapi ticket: ", err or tostring(res.status))
                break
              end
              local resbody = cjson.decode(res.body)
              if not resbody.ticket then
                ngx_log(ngx.ERR, "failed to update jsapi ticket: ", res.body)
                break
              end

              local ok, err = weredis.redis:setex(jsapiTicketKey, updateTime - 1, resbody.ticket)
              if not ok then
                ngx_log(ngx.ERR, "failed to set jsapi ticket: ", err)
                break
              end

              ngx_log(ngx.NOTICE, "succeed to set jsapi ticket: ", res.body)
              break

            until true
          end

        end
      )

      local ok, err = ngx_timer_at(pollingTime, updateAccessToken)
      if not ok then
        ngx_log(ngx.ERR, "failed to create the Access Token Updater: ", err)
        return
      end
    end

    local ok, err = ngx_timer_at(5, updateAccessToken)
    if not ok then
      ngx_log(ngx.ERR, "failed to create the Access Token Updater: ", err)
      return
    end
  end,
}

return setmetatable(_M, mt)
